package comp.racer.so;

import comp.racer.common.interfaces.ClienteIF;
import comp.racer.common.interfaces.ServidorIF;
import comp.racer.ip.Ranking;
import comp.racer.ip.TextoPersistencia;
import comp.racer.common.rn.Jogador;
import comp.racer.common.so.Localizador;

import comp.racer.common.rn.CompRacerException;
import comp.racer.rn.Partida;
import comp.racer.rn.SalaDeEspera;

import java.io.FileNotFoundException;
import java.lang.reflect.Method;
import java.net.MalformedURLException;
import java.net.SocketException;
import java.rmi.Naming;
import java.rmi.NotBoundException;
import java.rmi.RMISecurityManager;
import java.rmi.RemoteException;
import java.rmi.registry.LocateRegistry;
import java.rmi.registry.Registry;
import java.rmi.server.UnicastRemoteObject;
import java.util.Arrays;
import java.util.Collection;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.logging.Level;
import java.util.logging.Logger;
import javax.swing.JOptionPane;

/**
 *
 * @author david
 */
public class Servidor extends UnicastRemoteObject implements ServidorIF {

    // atributos especificados na FCR
    private SalaDeEspera salaDeEspera;
    private Map<Localizador, ClienteIF> localizadorClienteMap;
    private Map<String, Jogador> loginJogadorMap;
    private Partida partida;
    private Ranking ranking;
    private TextoPersistencia texto;

    // atributos adicionais
    private static Servidor servidorSingleton;
    private Localizador localizador;

    // constructores
    private Servidor() throws RemoteException, SocketException {
        super();
        setRanking(new Ranking());
        setSalaDeEspera(new SalaDeEspera());
        setLocalizadorClienteMap(new HashMap<Localizador, ClienteIF>());
        setLoginJogadorMap(new HashMap<String, Jogador>());
        setTexto(new TextoPersistencia());
      

        Integer port = Registry.REGISTRY_PORT;
        String serviceName = "COMPRacerService";
        setLocalizador(new Localizador(port, serviceName));
        
    }

    // metodos especificados na FCR
    public void iniciaServidor() {
        try {
            getTexto().inicializaTextos();
            setUpEnvironment();
            setUpService();
        } catch (FileNotFoundException ex) {
            Logger.getLogger(Servidor.class.getName()).log(Level.SEVERE, null, ex);
        } catch (RemoteException ex) {
            Logger.getLogger(Servidor.class.getName()).log(Level.SEVERE, null, ex);
        } catch (SocketException ex) {
            Logger.getLogger(Servidor.class.getName()).log(Level.SEVERE, null, ex);
        }


    }

    public void realizaConexao(Localizador clienteLocalizador) throws MalformedURLException, RemoteException, NotBoundException {
        System.out.println("\nCliente requisita conexao. Tentativa de conexao ao cliente: " + clienteLocalizador);
        ClienteIF newCliente = (ClienteIF) Naming.lookup(clienteLocalizador.toString());
        System.out.println("Conectado ao cliente: " + newCliente.getLocalizador());

        // adicionando o novo cliente no conjunto de clientes do servidor
        getLocalizadorClienteMap().put(newCliente.getLocalizador(), newCliente);
    }

    public Map<String, Jogador> validaEntradaSalaDeEspera(Localizador clienteLocalizador, String clienteLogin) throws RemoteException, CompRacerException {

        if( !getSalaDeEspera().isAcessivel() )
            throw new CompRacerException("Partida em andamento. Tente mais tarde!");

        System.out.println("Servidor: validaEntradaSalaDeEspera: " + clienteLogin);

        Jogador jogadorValidado = null;
        jogadorValidado = getSalaDeEspera().validaEntradaJogador(clienteLogin);

        System.out.println("\n");

        if (jogadorValidado != null) {
            this.notificaEntradaJogador(clienteLocalizador, jogadorValidado);

            return getSalaDeEspera().getLoginJogadorMap();
        } else {
            return null;
        }

    }

    public void notificaEntradaJogador(Localizador clienteLocalizador, Jogador jogadorValidado) {
        System.out.println("\nServidor: executando metodo notificaEntradaJogador:");

        ClienteIF clienteValidado = getLocalizadorClienteMap().get(clienteLocalizador);
        try {

            for (ClienteIF loopCliente : getLocalizadorClienteMap().values()) {

                boolean clienteNaSalaDeEspera = (getJogadorFromCliente(loopCliente) != null);

                if (!clienteValidado.equals(loopCliente) &&
                        clienteNaSalaDeEspera) {

                    System.out.println("cliente = " + loopCliente.getLocalizador() + " sendo notificado da entrada de jogadorValidado = " + jogadorValidado);

                    loopCliente.atualizaJogador(jogadorValidado);
                }
            }
        } catch (RemoteException ex) {
            JOptionPane.showMessageDialog(null, ex.getMessage(), "Erro", JOptionPane.ERROR_MESSAGE);
        }

        System.out.println("\n");
    }

    public void notificaSaidaJogador(Jogador jogadorDesconectado) throws RemoteException {
        if (jogadorDesconectado != null) {
            System.out.println("\nServidor: notificaSaidaJogador: " + jogadorDesconectado.getLogin());
            atualizaJogadores(jogadorDesconectado);
        }
    }

    public void notificaMudancaDeEstado(String clienteLogin) throws RemoteException {

        System.out.println("Servidor: notificaMudancaDeEstado :" +
                getSalaDeEspera().getLoginJogadorMap().get(clienteLogin));

        Jogador jogadorAtualizado = getSalaDeEspera().alteraEstado(clienteLogin);

        atualizaJogadores(jogadorAtualizado);

        if (getSalaDeEspera().isTodosJogadoresPraparados()) {
            iniciaPartida();
        }

        System.out.println("\n");

    }

    public void iniciaPartida() {
        System.out.println("Servidor: iniciando partida em todos os clientesPartida: ");

        getTexto().buscaTexto();
        String textoDaPartida = getTexto().toString();

        setPartida(new Partida(getTexto()));

        for (ClienteIF cliente : getLocalizadorClienteMap().values()) {
            try {

                Method runMetodo = cliente.getClass().getMethod("inicializaTelaPartida", String.class);
                ServidorThread servidorThread = new ServidorThread(cliente, runMetodo, textoDaPartida);
                servidorThread.start();

            } catch (Exception ex) {
                JOptionPane.showMessageDialog(null, ex.getMessage(), "Erro", JOptionPane.ERROR_MESSAGE);
            }
        }

        System.out.println("Iniciada partida!\n");
    }

    public void notificaProgressoJogador(Jogador jogador, int numCaracteresCorretos) throws RemoteException {

        System.out.println("Servidor:notificaProgressoJogador : " + jogador);
        Jogador jogadorAtualizado = getPartida().atualizaProgressoJogadores(jogador, numCaracteresCorretos);

        atualizaJogadores(jogadorAtualizado);

        if (getPartida().isPartidaEncerrada()) {
            Map<Jogador, Integer> jogadorPosicaoRankingMap = getPartida().getJogadorPosicaoRankingMap();
            encerraPartida(jogadorPosicaoRankingMap);

        }
    }

    public void encerraPartida(Map<Jogador, Integer> jogadorPosicaoRankingMap) {

        System.out.println("Servidor: encerraPartida\n");
        Collection<ClienteIF> listaClientes = getLocalizadorClienteMap().values();

        for (ClienteIF cliente : listaClientes) {
            try {

                Class[] ifs = jogadorPosicaoRankingMap.getClass().getInterfaces();
                List<Class> parametroslist = Arrays.asList(ifs);

                Method runMetodo = cliente.getClass().getMethod("encerraTelaPartida", parametroslist.get(0));
                ServidorThread servidorThread = new ServidorThread(cliente, runMetodo, jogadorPosicaoRankingMap);
                servidorThread.start();

            } catch (NoSuchMethodException ex) {
                JOptionPane.showMessageDialog(null, ex.getMessage(), "Erro", JOptionPane.ERROR_MESSAGE);
            }

        }

        System.out.println("Partida encerrada em todos os clientes\n");
        getSalaDeEspera().setAcessivel(true);
    }

    public void encerraServidor() {
        /*     Assinatura do Método: encerraServidor()
        Lógica da Responsabilidade: Encerra todas as atividades do servidor.Inicia servidor
         */
    }

    public List<Jogador> obtemRanking() throws RemoteException {

        return getRanking().buscaRanking();
    

    }

    public void realizaDesconexao(Localizador clienteLocalizador) {
        try {
            System.out.println("\nCliente requisita desconexao. Tentativa de desconexao com o cliente: " + clienteLocalizador);

            ClienteIF clienteDesconectado = getLocalizadorClienteMap().get(clienteLocalizador);
            getLocalizadorClienteMap().remove(clienteLocalizador);

            Jogador jogadorDesconectado = getJogadorFromCliente(clienteDesconectado);

            if (jogadorDesconectado != null) {
                jogadorDesconectado.setEstado(Jogador.FORA);
                getLoginJogadorMap().remove(jogadorDesconectado.getLogin());
            }

            notificaSaidaJogador(jogadorDesconectado);

            System.out.println("Cliente " + clienteLocalizador + " desconectado!\n");

            // no caso de todos sairem na hora da partida
            if( !getSalaDeEspera().isAcessivel() && getLoginJogadorMap().size() == 0) {
                encerraPartida(null);
            }
        } catch (RemoteException ex) {
            JOptionPane.showMessageDialog(null, ex.getMessage(), "Erro", JOptionPane.ERROR_MESSAGE);
        }

    }

    // metodos publicos adicionais
    public static Servidor getInstancia() {
        if (servidorSingleton == null) {
            try {
                servidorSingleton = new Servidor();
            } catch (RemoteException ex) {
                return null;
            } catch (SocketException ex) {
                Logger.getLogger(Servidor.class.getName()).log(Level.SEVERE, null, ex);
            }
        }
        return servidorSingleton;
    }

    // metodos privados adicionais
    private static void setUpEnvironment() {
        System.out.println("1 - Inicializando variaveis de ambiente.");

        String projectPath = System.getProperty("user.dir");

        System.setProperty("java.security.policy", "file:" + projectPath + "/servidor.policy");
        System.setProperty("java.rmi.server.codebase", "file:" + projectPath);

        if (System.getSecurityManager() == null) {
            System.setSecurityManager(new RMISecurityManager());
        }

        System.out.println("2 - Variaveis de ambiente inicializadas.");
    }

    private static void setUpService() throws RemoteException, SocketException {
        Servidor servidor = getInstancia();

        System.out.println("3 - Registrando o servico.");

        // com esse comando ele cria um registro e jah coloca no ar o programa
        Registry registry = LocateRegistry.createRegistry(servidor.getLocalizador().getPort());
        registry.rebind(servidor.getLocalizador().getServiceName(), servidor);

        System.out.println("4 - Servidor rodando em: " + servidor.getLocalizador());
    }

    private void atualizaJogadores(Jogador jogadorAtualizado) throws RemoteException {
        for (ClienteIF cliente : getLocalizadorClienteMap().values()) {
            cliente.atualizaJogador(jogadorAtualizado);
        }
    }

    // getters e setters
    public Localizador getLocalizador() throws RemoteException {
        return localizador;
    }

    public void setLocalizador(Localizador localizador) throws RemoteException {
        this.localizador = localizador;
    }

    public Map<Localizador, ClienteIF> getLocalizadorClienteMap() {
        return localizadorClienteMap;
    }

    public void setLocalizadorClienteMap(Map<Localizador, ClienteIF> localizadorClienteMap) {
        this.localizadorClienteMap = localizadorClienteMap;
    }

    public Partida getPartida() {
        return partida;
    }

    public void setPartida(Partida partida) {
        this.partida = partida;
    }

    public Ranking getRanking() {
        return ranking;
    }

    public void setRanking(Ranking ranking) {
        this.ranking = ranking;
    }

    public SalaDeEspera getSalaDeEspera() {
        return salaDeEspera;
    }

    public void setSalaDeEspera(SalaDeEspera salaDeEspera) {
        this.salaDeEspera = salaDeEspera;
    }

    public TextoPersistencia getTexto() {
        return texto;
    }

    public void setTexto(TextoPersistencia texto) {
        this.texto = texto;
    }

    public Map<String, Jogador> getLoginJogadorMap() {
        return loginJogadorMap;
    }

    public void setLoginJogadorMap(Map<String, Jogador> loginJogadorMap) {
        this.loginJogadorMap = loginJogadorMap;
    }

    // refatoracao para diminuicao e reaproveitamento no codigo final
    private Jogador getJogadorFromCliente(ClienteIF clienteDesconectado) throws RemoteException {
        //TODO analisar as situacoes de qd o cliente acaba a partida e volta p tela de conexao e
        // automaticamente chama-se o realiza desconexao e por ai vai!

        if (clienteDesconectado != null) {
            String loginCliente = clienteDesconectado.getLogin();
            Jogador jogador = getLoginJogadorMap().get(loginCliente);

            return jogador;
        }

        return null;
    }

    // main
    public static void main(String args[]) {

        getInstancia().iniciaServidor();
    }
}
